using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

using Flood.Extensions;
using Flood.FloodStrategies;

namespace Flood
{
	public sealed class Puzzle
	{
		private readonly Grid grid;
		private readonly Dictionary<char, Shape> moistShapes = new Dictionary<char, Shape>();
		private readonly PuzzleConstants puzzleConstants;
		private readonly Square topLeftSquare;
		public char[] AdjacentColors
		{
			get { return moistShapes.Keys.ToArray(); }
		}

		public Puzzle(string encodedPuzzle, PuzzleConstants puzzleConstants)
			: this(new Grid(encodedPuzzle,
			                puzzleConstants.Rows,
			                puzzleConstants.Columns),
			       puzzleConstants)
		{
		}

		public Puzzle(Grid grid, PuzzleConstants puzzleConstants)
		{
#if DEBUG
			if (puzzleConstants.DisplayMoves)
			{
				WriteOutput("new puzzle");
			}
#endif
			this.grid = grid;
			this.puzzleConstants = puzzleConstants;
			topLeftSquare = grid[0,
			                     0];
			Shape shape = topLeftSquare.Shape;
			if (shape.Status != Status.Flooded)
			{
				MakeMoistIfDry(shape);
				Flood(shape.Color);
			}
			else
			{
				AddMoistAdjacentShapes(shape.AdjacentShapes);
			}
		}

		public string Encode()
		{
			return grid.Encode();
		}

		public Result Flood(IFloodStrategy strategy, int maxFloods)
		{
#if DEBUG
			if (puzzleConstants.DisplayMoves)
			{
				WriteOutput(ToString());
			}
#endif
			List<char> steps = new List<char>();
			int totalSquares = puzzleConstants.Rows * puzzleConstants.Columns;
			while (steps.Count < maxFloods &&
			       topLeftSquare.Shape.Parts.Count < totalSquares)
			{
				Result result = strategy.GetNextToFlood(moistShapes.Values,
				                                        grid);
				int stepsAvailable = maxFloods - steps.Count;
				if (result.IsSolved &&
				    stepsAvailable > result.FillSteps.Count)
				{
					steps.AddRange(result.FillSteps);
					return new Result(steps,
					                  true);
				}
				foreach (char step in result.FillSteps)
				{
					steps.Add(step);
					Flood(step);
					if (steps.Count >= maxFloods ||
					    topLeftSquare.Shape.Parts.Count == totalSquares)
					{
						break;
					}
#if DEBUG
					if (puzzleConstants.DisplayMoves)
					{
						WriteOutput("after step: " + steps.Count);
						WriteOutput(ToString());
					}
#endif
				}
			}
			return new Result(steps,
			                  topLeftSquare.Shape.Parts.Count == totalSquares);
		}

		public override string ToString()
		{
			StringBuilder sb = new StringBuilder();
			sb.AppendLine(" moist:");
			moistShapes.Values.ForEach(x => sb.AppendLine(x.ToString()));
			sb.Append(grid.ToString());
			return sb.ToString();
		}

		private void Flood(char color)
		{
			grid.DisableCachingOnTouched();
			topLeftSquare.Shape.Color = color;
			Shape moistShape;
			if (moistShapes.TryGetValue(color,
			                            out moistShape))
			{
				moistShapes.Remove(color);
				Flood(moistShape,
				      color);
				if (topLeftSquare.Shape.Id != moistShape.Id)
				{
					topLeftSquare.Shape.Combine(moistShape);
				}
			}
			grid.EnableCachingOnTouched();
		}

		private void Flood(Shape shape, char color)
		{
			if (shape.Status != Status.Moist)
			{
				return;
			}
#if DEBUG
			if (puzzleConstants.DisplayMoves)
			{
				WriteOutput("flooding with color " + color);
			}
#endif
			shape.Status = Status.Flooded;
			shape.Color = color;
			MakeAllDryAdjacentShapesMoist(shape);
		}

		private void MakeAllDryAdjacentShapesMoist(Shape shape)
		{
			shape.AdjacentShapes.ForEach(MakeMoistIfDry);
		}

		private void AddMoistAdjacentShapes(IEnumerable<Shape> shapes)
		{
			foreach (Shape shape in shapes.Where(x => x.Status == Status.Moist))
			{
				if (!moistShapes.ContainsKey(shape.Color))
				{
					moistShapes.Add(shape.Color,
					                shape);
				}
			}
		}

		private void MakeMoistIfDry(Shape shape)
		{
			if (shape.Status != Status.Dry)
			{
				return;
			}
			shape.Status = Status.Moist;
			Shape moistShape;
			if (moistShapes.TryGetValue(shape.Color,
			                            out moistShape))
			{
				moistShape.Combine(shape);
			}
			else
			{
				moistShapes.Add(shape.Color,
				                shape);
			}
		}

		[Conditional("DEBUG")]
		[Conditional("DETAILED")]
		private static void WriteOutput(string message)
		{
			Console.WriteLine(message);
		}
	}
}